package tests.experiments.resourcerelation;

import jeconbond.automata.*;
import jeconbond.experiment.market.resourcerelations.generate.AuditFlowProdAgentsCreator;
import jeconbond.experiment.market.resourcerelations.generate.BranchingProdAutomataGenerator;
import jeconbond.experiment.market.resourcerelations.generate.GoalGroupComplementaryBasedProfitCostGen;
import jeconbond.experiment.market.resourcerelations.generate.GoalGroupDependencyBasedProfitCostGen;
import jeconbond.experiment.market.generate.DefaultProfCostManagerFactory;
import jeconbond.economic.EconomicEngine;
import jeconbond.economic.agent.AutomataAgent;
import jeconbond.economic.agent.service.IPostActServiceAgent;
import jeconbond.economic.market.strategy.ObserveCostsProfitMerchantDecorator;
import jeconbond.economic.resources.*;
import jeconbond.economic.systems.market.*;
import jeconbond.experiment.market.resourcerelations.BrosnenExperimentSettings;
import jeconbond.experiment.market.resourcerelations.GraigExperimentSettings;
import jeconbond.experiment.market.resourcerelations.process.IBrosnenExperimentContext;
import jeconbond.experiment.market.resourcerelations.process.IGraigExperimentContext;
import jeconbond.experiment.JEBExperimentFlow;
import jeconbond.experiment.generate.ProfitableNormFormGenerateDecorator;
import jeconbond.experiment.process.IJEBExperimentContext;
import junit.framework.Assert;
import junit.framework.TestCase;

import java.util.*;

import laboratoryQ.experiment.IExperiment;
import laboratoryQ.flatness.Point;

public class TestGenerators extends TestCase {
	private static final IExperiment EXPERIMENT = new JEBExperimentFlow();

	public void testBranchingProdAutomataGenerator() {
		BrosnenExperimentSettings settings = new BrosnenExperimentSettings();
		settings.initialise();
		settings.rrsFactory = new DefaultMarketRRSFactory();
		settings.stepCount = -2;
		settings.stepSize = -2;
		settings.initResourceExceedPercent = 1.1;
		settings.concurrentPricePercent = Double.NaN;
		settings.observeCostsTraceLength = -2;
		settings.agentGreedPercent = Double.NaN;
		settings.initMoney = Double.NaN;
		settings.agentsCount = 10;
		settings.resources = ResourceUtils.generateResources("r", 3);
		settings.produceProfit = 1.23;
		
		settings.chainLength = 7;
		settings.branchesCount = 5;
		settings.postBranchCount = 4;
		settings.experiment = EXPERIMENT;
		settings.randomSeed = 0.0;
		
		settings.zeroPoint = new Point(0.0, 0.0);
		settings.purchaseExceedDayBudget = -2.0;
		settings.purchaseExceedPassivePeriod = -2;
		settings.purchaseExceedUseTrackLength = -2;
		settings.utiliseExceedPeriod = -2;
		settings.selectPropositionsPart = -1;
		settings.feramonHistoryLength = -2;
		settings.antAgentCount = -2;
		settings.profitCostManagerFactory = new DefaultProfCostManagerFactory();
		settings.setCommoditiesDependGroups(null);
		settings.setCommoditiesDependGroupsPower(-2.0);
		settings.goalResourceIndex = -2;
		settings.timeStepX = -2;
		settings.speculantPropCountMarketPart = 0.0;
		settings.speculantGoalValueMarketPart = 0.0;
		settings.validate();
		
		IJEBExperimentContext context = (IJEBExperimentContext)
				settings.getExperimentContextFactory().newContext(settings);
		ProfitableNormFormGenerateDecorator branchingProdAutomataGenerator =
			new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());
		
		INode[] automatas = branchingProdAutomataGenerator.generate(context);

		ResourceStorrage costs = new ResourceStorrage();
		ResourceStorrage profit = new ResourceStorrage();
		for (INode root : automatas) {
			AutomataUtils.dfsCosts(root, costs, profit);
		}
		for (IResourceType rt : settings.resources) {
			double c = costs.howMuchWasStored(rt);
			double p = profit.howMuchWasStored(rt);
			Assert.assertTrue(Math.abs(settings.produceProfit - p / c) < 1e-5);
		}

		for (INode root : automatas) {
			Assert.assertEquals(
					settings.branchesCount, 
					root.getOutcomes().size()
			);
			Assert.assertEquals(
					settings.chainLength, 
					computeChainLength(root)
			);
			int[] postBranchesSizes = computePostBranchesSizes(root);
			Assert.assertEquals(settings.postBranchCount, postBranchesSizes.length);
			int[] pathR2RSizes = computePathsRoot2RootSizes(root);
			Assert.assertEquals(
					settings.branchesCount + 
					settings.postBranchCount, 
					pathR2RSizes.length
			);
			for (int i = 0; i < pathR2RSizes.length; i++) {
				Assert.assertEquals(settings.chainLength, pathR2RSizes[i]);
			}
		}
	}

	private int[] computePathsRoot2RootSizes(INode root) {
		List<List<IOutcome>> pathsList = AutomataUtils.dfsRoot2RootPath(root);
		int[] result = new int[pathsList.size()];
		for (int i = 0; i < result.length; i++) {
			result[i] = pathsList.get(i).size();
		}
		return result;
	}

	private int[] computePostBranchesSizes(INode root) {
		List<Integer> list = new ArrayList<Integer>();
		dfsRoot(root, list, root);
		int[] result = new int[list.size()];
		for (int i = 0; i < result.length; i++) {
			result[i] = list.get(i);
		}
		return result;
	}

	private void dfsRoot(
			INode cur,
			List<Integer> postBs,
			INode root 
	) {
		for (IOutcome out : cur.getOutcomes()) {
			dfs(out.getDestination(), postBs, root);
		}
	}

	private void dfs(
			INode cur,
			List<Integer> postBs,
			INode root 
	) {
		if (cur == root) {
			return;
		}
		if (cur.getOutcomes().size() > 1) {
			postBs.add(cur.getOutcomes().size());
		}
		for (IOutcome out : cur.getOutcomes()) {
			dfs(out.getDestination(), postBs, root);
		}
	}

	private int computeChainLength(INode root) {
		return AutomataUtils.easyTracePlan(root).size();
	}
	
	private static class ResourceProvider implements IPostActServiceAgent, ISellsListener {
		private static final int FLOW_COUNT = 3;
		private IMarketResourceRedestributionSystem mrrs;
		private IResourceType[] resources;
		private IResourceType[][] necRes;
		private IResourceType[] prodRes;
		private Queue<IResourceType> spyedResQueue = new LinkedList<IResourceType>();
		private IMoneyAccount money = new MoneyAccount(Double.MAX_VALUE);
		private int spyResQueueSize;
		private ISellProposition[] propositions;
		private Map<IResourceType, Double> resource2price = 
			new HashMap<IResourceType, Double>();
		private ResourceStorrage storrage = new ResourceStorrage();
		
		public ResourceProvider(
				IMarketResourceRedestributionSystem mrrs,
				IResourceType[] resources,
				int spyResQueueSize
		) {
			this.mrrs = mrrs;
			this.resources = resources;
			this.spyResQueueSize = spyResQueueSize;
			propositions = new ISellProposition[resources.length];
			
			necRes = new IResourceType[][] {
					new IResourceType[] { resources[0], resources[1], resources[2] },
					new IResourceType[] { resources[3], resources[4], resources[5] },
					new IResourceType[] { resources[6], resources[7], resources[8] },
			};
			prodRes = new IResourceType[]{
					resources[9],
					resources[10],
					resources[11],
			};
			
			mrrs.addSellsListener(this);
			initialiseResource2price(new double[] { 1, 2, 3 });
			initialisePropositions();
		}

		private void initialisePropositions() {
			storrage.clear();
			for (int i = 0; i < resources.length; i++) {
				propositions[i] = new  SellProposition(
						resources[i],
						resource2price.get(resources[i]),
						storrage,
						money
				);
				storrage.storeResource(resources[i], 1e5);
			}
		}
		
		private void initialiseResource2price(double[] costMul) {
			for (int i = 0; i < necRes.length; i++) {
				for (IResourceType rt : necRes[i]) {
					resource2price.put(rt, costMul[i]);
				}
			}
			for (IResourceType rt : prodRes) {
				resource2price.put(rt, 1.0);
			}
		}

		@Override
		public void processPreProduceStage() {
		}
		
		@Override
		public void processProduceStage() {
		}
		
		@Override
		public void processPostProduceStage() {
			for (ISellProposition prop : propositions) {
				mrrs.registerProposition(prop);
			}
		}
		
		@Override
		public void onSellPerformed(
				ISellProposition proposition, 
				double value,
				IMoneyAccount buyerMoney
		) {
			if (buyerMoney != money) {
				spyedResQueue.add(proposition.getType());
				if (spyedResQueue.size() > spyResQueueSize) {
					spyedResQueue.poll();
				}
			}
		}
		
		public String usedResources2string() {
			String result = "";
			for (String rtName : lastUsedResourcesList()) {
				result += rtName + ";";
			}
			return result;
		}
		
		public void makeCheapSecondFlow() {
			initialiseResource2price(new double[] { 1, 0.5, 3 });
			initialisePropositions();
		}
		
		public void makeProfitablethirdFlow() {
			storrage.storeResource(
					new ResourceBasket(prodRes[2], 1.0)
			);
			int propCount = 5;
			ISellProposition[] newPropositions = new ISellProposition[propositions.length + propCount];
			System.arraycopy(propositions, 0, newPropositions, 0, propositions.length);
			for (int i = 0; i < propCount; i++) {
				newPropositions[propositions.length + i] = new SellProposition(
						prodRes[2],
						1e6,
						storrage,
						money
				);
			}
			propositions = newPropositions;
		}
		
		public INode create3FlowAutomata() {
			INode root = new Node("root");
			for (int i = 0; i < FLOW_COUNT; i++) {
				IResourceType[] curNecRes = necRes[i];
				IResourceType curProdRes = prodRes[i];
				INode prev = root;
				for (int j = 0; j < curNecRes.length; j++) {
					IResourceType necRes = curNecRes[j];
					INode currentNode = new Node("Br" + 1 + "N" + j);
					IOutcome out = new Outcome(currentNode, new ResourceBasket(necRes, 1.0));
					prev.getOutcomes().add(out);
					prev = currentNode;
				}
				IOutcome out = new Outcome(root);
				out.setProducedResources(new ResourceBasket(curProdRes, 1.0));
				prev.getOutcomes().add(out);
			}
			return root;
		}
		
		private List<String> lastUsedResourcesList() {
			Set<String> set = new HashSet<String>();
			for (IResourceType rt : spyedResQueue) {
				set.add(rt.getName());
			}
			ArrayList<String> resources = new ArrayList<String>(set);
			Collections.sort(resources);
			return resources;
		}
	}
	
	public void testAuditFlowProdAgentsCreator() {
		BrosnenExperimentSettings settings = new BrosnenExperimentSettings();
		settings.initialise();
		settings.rrsFactory = new tests.fakes.StoreOneMarketRRSFactory();
		settings.stepCount = -2;
		settings.stepSize = -2;
		settings.initResourceExceedPercent = 1.1;
		settings.concurrentPricePercent = 0.0;
		settings.observeCostsTraceLength = 10;
		settings.agentGreedPercent = 1.1;
		settings.initMoney = 1e7;
		settings.agentsCount = 1;
		settings.resources = ResourceUtils.generateResources("r", 12);
		settings.produceProfit = 1.23;
		
		settings.chainLength = 7;
		settings.branchesCount = 4;
		settings.postBranchCount = 3;
		settings.experiment = EXPERIMENT;
		settings.randomSeed = 0.0;

		settings.zeroPoint = new Point(0.0, 0.0);
		settings.purchaseExceedDayBudget = -2.0;
		settings.purchaseExceedPassivePeriod = -2;
		settings.purchaseExceedUseTrackLength = -2;
		settings.utiliseExceedPeriod = -2;
		settings.selectPropositionsPart = -1;
		settings.feramonHistoryLength = -2;
		settings.antAgentCount = -2;
		settings.profitCostManagerFactory = new DefaultProfCostManagerFactory();
		settings.setCommoditiesDependGroups(null);
		settings.setCommoditiesDependGroupsPower(-2.0);
		settings.goalResourceIndex = -2;
		settings.timeStepX = -2;
		settings.speculantPropCountMarketPart = 0.0;
		settings.speculantGoalValueMarketPart = 0.0;
		settings.validate();
		int stepsPerExperiment = 30;
		
		IJEBExperimentContext experimentContext = (IJEBExperimentContext)
				settings.getExperimentContextFactory().newContext(settings);
		DefaultMarketResourceRedestributionSystem mrrs =
			(DefaultMarketResourceRedestributionSystem) settings.rrsFactory.newRRS(experimentContext);
		
		AuditFlowProdAgentsCreator auditFlowProdAgentsCreator =
			new AuditFlowProdAgentsCreator();
		ResourceProvider resourceProvider = new ResourceProvider(
				mrrs, 
				settings.resources, 
				settings.chainLength * 3
		);
		INode[] automatas = new INode[] { resourceProvider.create3FlowAutomata() };
		AutomataAgent[] prodAgs = auditFlowProdAgentsCreator.automata2agents(automatas, experimentContext);
		Assert.assertEquals(1, prodAgs.length);
		AutomataAgent prodAg = prodAgs[0];
		
		EconomicEngine engine = new EconomicEngine();
		ObserveCostsProfitMerchantDecorator observeCPMerch = 
			(ObserveCostsProfitMerchantDecorator) prodAg.getMerchant();
		engine.addAgents(
				observeCPMerch,
				prodAg,
				resourceProvider
		);
		
		engine.processSteps(stepsPerExperiment * settings.chainLength, mrrs);
		Assert.assertEquals("r0;r1;r2;", resourceProvider.usedResources2string());
		resourceProvider.makeCheapSecondFlow();
		
		engine.processSteps(stepsPerExperiment * settings.chainLength, mrrs);
		Assert.assertEquals("r3;r4;r5;", resourceProvider.usedResources2string());
		resourceProvider.makeProfitablethirdFlow();
		
		engine.processSteps(stepsPerExperiment * settings.chainLength, mrrs);
		Assert.assertEquals("r6;r7;r8;", resourceProvider.usedResources2string());
	}
	
	/**
	 * test 4 bug: a number of resource baskets of same type in one requirement list. 
	 */
	public void testResourceReqIndividuality() {
		BrosnenExperimentSettings settings = new BrosnenExperimentSettings();
		settings.initialise();
		settings.rrsFactory = new DefaultMarketRRSFactory();
		settings.stepCount = -2;
		settings.stepSize = -2;
		settings.initResourceExceedPercent = 1.1;
		settings.concurrentPricePercent = Double.NaN;
		settings.observeCostsTraceLength = -2;
		settings.agentGreedPercent = Double.NaN;
		settings.initMoney = Double.NaN;
		settings.agentsCount = 100;
		settings.resources = ResourceUtils.generateResources("r", 3);
		settings.produceProfit = 1.23;
		
		settings.chainLength = 10;
		settings.branchesCount = 3;
		settings.postBranchCount = 3;
		settings.experiment = EXPERIMENT;

		settings.zeroPoint = new Point(0.0, 0.0);
		settings.randomSeed = 0.0;
		settings.purchaseExceedDayBudget = -2.0;
		settings.purchaseExceedPassivePeriod = -2;
		settings.purchaseExceedUseTrackLength = -2;
		settings.utiliseExceedPeriod = -2;
		settings.selectPropositionsPart = -1.0;
		settings.feramonHistoryLength = -2;
		settings.antAgentCount = -2;
		settings.profitCostManagerFactory = new DefaultProfCostManagerFactory();
		settings.setCommoditiesDependGroups(null);
		settings.setCommoditiesDependGroupsPower(-2.0);
		settings.goalResourceIndex = -2;
		settings.timeStepX = -2;
		settings.speculantPropCountMarketPart = 0.0;
		settings.speculantGoalValueMarketPart = 0.0;
		settings.validate();
		
		IJEBExperimentContext context = (IJEBExperimentContext)
				settings.getExperimentContextFactory().newContext(settings);
		ProfitableNormFormGenerateDecorator branchingProdAutomataGenerator =
			new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());
		INode[] automatas = branchingProdAutomataGenerator.generate(context);
		List<IOutcome> allouts = new ArrayList<IOutcome>();
		for (INode root : automatas) {
			List<IOutcome> outs = AutomataUtils.dfsGetAllOutcomes(root);
			allouts.addAll(outs);
		}
		for (IOutcome out : allouts) {
			int maxRepetition = getMaxRequireRepetition(out, 1);
			Assert.assertEquals(1, maxRepetition);
		}
	}

	public void testDependsProbabGen() {
		BrosnenExperimentSettings settings = 
			new BrosnenExperimentSettings();
		settings.initialise();
		
		settings.stepSize = 20;
		settings.stepCount = 10;
		settings.agentsCount = 70;
		settings.initMoney = 50.0;
		settings.initResourceExceedPercent = 1.2;
		settings.antAgentCount = 0;
		
		settings.chainLength = 15;
		settings.branchesCount = 3;
		settings.postBranchCount = 2;
		
		settings.agentGreedPercent = 2.0;
		settings.produceProfit = 1.1;
		settings.concurrentPricePercent = 0.3;
		settings.observeCostsTraceLength = 15;
		
		settings.rrsFactory = new DefaultMarketRRSFactory();
		settings.resources = ResourceUtils.generateResources("r", 9);
		settings.experiment = EXPERIMENT; 
		settings.randomSeed = 0.0; 
		settings.zeroPoint = new Point(0.0, 0.0);
		
		settings.purchaseExceedDayBudget = -2.0;
		settings.purchaseExceedPassivePeriod = -2;
		settings.purchaseExceedUseTrackLength = -2;
		settings.utiliseExceedPeriod = -2;
		settings.selectPropositionsPart = -1.0;
		settings.feramonHistoryLength = 5;
		int[][] groups = new int[][] {
				new int[] { 0, 1, 2, 3 },
				new int[] { 4, 5, 6, 7, 8 },
		};
		double value = 10.0;
		settings.setCommoditiesDependGroupsPower(value);
		settings.setCommoditiesDependGroups(groups);
		settings.profitCostManagerFactory = new DefaultProfCostManagerFactory();
		settings.goalResourceIndex = -2;
		settings.timeStepX = -2;
		settings.speculantPropCountMarketPart = 0.0;
		settings.speculantGoalValueMarketPart = 0.0;
		settings.validate();
		
		IBrosnenExperimentContext experimentContext = (IBrosnenExperimentContext)
			settings.getExperimentContextFactory().newContext(settings);
		ProfitableNormFormGenerateDecorator automataGenerator =
				new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());
		INode[] auto0 = automataGenerator.generate(experimentContext);
		
		settings.profitCostManagerFactory = GoalGroupDependencyBasedProfitCostGen.FACTORY_INSTANCE;
		
		automataGenerator = new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());
		INode[] auto1 = automataGenerator.generate(
				(IJEBExperimentContext) settings.getExperimentContextFactory().newContext(settings)
		);
		
		IResourceType[] resources = settings.resources;
		double mul = value / 2;
		int probs0 = assertDepentProbabsCorrect(resources, auto0, groups, mul, false);
		int probs1 = assertDepentProbabsCorrect(resources, auto1, groups, mul, true);
		
		Assert.assertEquals(resources.length, probs0);
		Assert.assertEquals(0, probs1);
	}

	public void testNormalisation() {
		BrosnenExperimentSettings settings =
			new BrosnenExperimentSettings();
		settings.initialise();

		settings.stepSize = 20;
		settings.stepCount = 10;
		settings.agentsCount = 70;
		settings.initMoney = 50.0;
		settings.initResourceExceedPercent = 1.2;
		settings.antAgentCount = 0;

		settings.chainLength = 15;
		settings.branchesCount = 3;
		settings.postBranchCount = 2;

		settings.agentGreedPercent = 2.0;
		settings.produceProfit = 1.1;
		settings.concurrentPricePercent = 0.3;
		settings.observeCostsTraceLength = 15;

		settings.rrsFactory = new DefaultMarketRRSFactory();
		settings.resources = ResourceUtils.generateResources("r", 9);
		settings.experiment = EXPERIMENT;
		settings.randomSeed = 0.0;
		settings.zeroPoint = new Point(0.0, 0.0);

		settings.purchaseExceedDayBudget = -2.0;
		settings.purchaseExceedPassivePeriod = -2;
		settings.purchaseExceedUseTrackLength = -2;
		settings.utiliseExceedPeriod = -2;
		settings.selectPropositionsPart = -1.0;
		settings.feramonHistoryLength = 5;
		int[][] groups = new int[][] {
				new int[] { 0, 1, 2, 3 },
				new int[] { 4, 5, 6, 7, 8 },
		};
		double value = 10.0;
		settings.setCommoditiesDependGroupsPower(value);
		settings.setCommoditiesDependGroups(groups);
		settings.profitCostManagerFactory = GoalGroupDependencyBasedProfitCostGen.FACTORY_INSTANCE;
		settings.goalResourceIndex = -2;
		settings.timeStepX = -2;
		settings.speculantPropCountMarketPart = 0.0;
		settings.speculantGoalValueMarketPart = 0.0;
		settings.validate();

		ProfitableNormFormGenerateDecorator automataGenerator =
				new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());

		INode[] roots = automataGenerator.generate(
				(IJEBExperimentContext) settings.getExperimentContextFactory().newContext(settings)
		);

		for (INode root : roots) {
			List<IOutcome> outs = AutomataUtils.dfsGetAllOutcomes(root);
			for (IOutcome o : outs) {
				ResourceBasket[] req = o.getRequirements();
				int n = req.length;
				for (int i = 0; i < n; i++) {
					for (int j = i + 1; j < n; j++) {
						Assert.assertFalse(req[i].type == req[j].type);
					}
				}
			}
		}
	}

	private int assertDepentProbabsCorrect(
			IResourceType[] resources,
			INode[] auto, 
			int[][] groups,
			double mul,
			boolean trueAssert
	) {
		Map<IResourceType, Integer> r2pos = new HashMap<IResourceType, Integer>();
		int n = resources.length;
		for (int i = 0; i < n; i++) {
			r2pos.put(resources[i], i);
		}
		double[][] appear = new double[n][];
		for (int i = 0; i < n; i++) {
			appear[i] = new double[n];
		}
		for (INode root : auto) {
			List<IOutcome> allouts = AutomataUtils.dfsGetAllOutcomes(root);
			double[] curappear = new double[n];
			ResourceStorrage prodStorr = new ResourceStorrage();
			for (IOutcome out : allouts) {
				for (ResourceBasket rb : out.getRequirements()) {
					int pos = r2pos.get(rb.type);
					curappear[pos] += rb.value;
				}
				for (ResourceBasket rb : out.getProducedResources()) {
					prodStorr.storeResource(rb);
				}
			}
			for (ResourceBasket goalRB : prodStorr.listBaskets()) {
				int pos = r2pos.get(goalRB.type);
				for (int i = 0; i < n; i++) {
					appear[pos][i] += curappear[i] / goalRB.value;
				}
			}
		}
		int notGoodCount = 0;
		for (int[] g : groups) {
			for (int p : g) {
				double ingr = 0.0;
				for (int q : g) {
					ingr += appear[p][q];
				}
				double outgr = 0.0;
				for (int q = 0; q < n; q++) {
					if (Arrays.binarySearch(g, q) > -1) continue;
					outgr += appear[p][q];
				}
				ingr /= g.length;
				outgr /= n - g.length;
				boolean good = ingr > outgr * mul;
				if (trueAssert) {
					Assert.assertTrue(good);
				}
				if (!good) {
					notGoodCount++;
				}
			}
		}
		return notGoodCount;
	}

	private int getMaxRequireRepetition(IOutcome out, int min) {
		Map<IResourceType, Integer> map = new HashMap<IResourceType, Integer>();
		for (ResourceBasket rb : out.getRequirements()) {
			int appearence = get(map, rb.type);
			appearence++;
			map.put(rb.type, appearence);
		}
		int result = min;
		for (int v : map.values()) {
			result = Math.max(result, v);
		}
		return result;
	}

	private int get(Map<IResourceType, Integer> map, IResourceType type) {
		if (!map.containsKey(type)) {
			return 0;
		}
		return map.get(type);
	}

	public void testComplementaryProbabGen() {
		GraigExperimentSettings settings =
			new GraigExperimentSettings();
		settings.initialise();
		
		settings.stepSize = 20;
		settings.stepCount = 10;
		settings.agentsCount = 70;
		settings.initMoney = 50.0;
		settings.initResourceExceedPercent = 1.2;
		settings.antAgentCount = 0;
		
		settings.chainLength = 15;
		settings.branchesCount = 3;
		settings.postBranchCount = 2;
		
		settings.agentGreedPercent = 2.0;
		settings.produceProfit = 1.1;
		settings.concurrentPricePercent = 0.3;
		settings.observeCostsTraceLength = 15;
		
		settings.rrsFactory = new DefaultMarketRRSFactory();
		settings.resources = ResourceUtils.generateResources("r", 9);
		settings.experiment = EXPERIMENT; 
		settings.randomSeed = 0.0; 
		settings.zeroPoint = new Point(0.0, 0.0);
		
		settings.purchaseExceedDayBudget = -2.0;
		settings.purchaseExceedPassivePeriod = -2;
		settings.purchaseExceedUseTrackLength = -2;
		settings.utiliseExceedPeriod = -2;
		settings.selectPropositionsPart = -1.0;
		settings.feramonHistoryLength = 5;
		int[][] groups = new int[][] {
				new int[] { 0, 1, 2, 3 },
				new int[] { 4, 5, 6, 7, 8 },
		};
		double value = 10.0;
		settings.setCommoditiesComplementaryGroupsPower(value);
		settings.setCommoditiesComplementaryGroups(groups);
		settings.profitCostManagerFactory = new DefaultProfCostManagerFactory();
		settings.goalResourceIndex = -2;
		settings.timeStepX = -2;
		settings.speculantPropCountMarketPart = 0.0;
		settings.speculantGoalValueMarketPart = 0.0;
		settings.validate();
		
		IGraigExperimentContext experimentContext = (IGraigExperimentContext)
			settings.getExperimentContextFactory().newContext(settings);
		ProfitableNormFormGenerateDecorator automataGenerator = 
			new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());
		INode[] auto0 = automataGenerator.generate(experimentContext);
		
		settings.profitCostManagerFactory = GoalGroupComplementaryBasedProfitCostGen.FACTORY_INSTANCE;
		
		automataGenerator = new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());
		INode[] auto1 = automataGenerator.generate(
				(IJEBExperimentContext) settings.getExperimentContextFactory().newContext(settings)
		);
		
		IResourceType[] resources = settings.resources;
		double mul = value / 3;
		int probs0 = assertComplementaryProbabsCorrect(resources, auto0, groups, mul, false);
		int probs1 = assertComplementaryProbabsCorrect(resources, auto1, groups, mul, true);
		
		Assert.assertEquals(resources.length, probs0);
		Assert.assertEquals(0, probs1);
	}
	
	private int assertComplementaryProbabsCorrect(
			IResourceType[] resources,
			INode[] automatas, 
			int[][] groups,
			double mul,
			boolean trueAssert
	) {
		Map<IResourceType, Integer> r2pos = new HashMap<IResourceType, Integer>();
		int n = resources.length;
		for (int i = 0; i < n; i++) {
			r2pos.put(resources[i], i);
		}
		double[][] compl = new double[n][n];
		for (INode root : automatas) {
			List<List<IOutcome>> profPaths = AutomataUtils.dfsRoot2RootPath(root);
			for (List<IOutcome> path : profPaths) {
				for (IOutcome out0 : path) {
					if (out0.getRequirements().length == 0) {
						continue;
					}
					ResourceBasket rb0 = out0.getRequirements()[0];
					int ind0 = r2pos.get(rb0.type);
					for (IOutcome out1 : path) {
						if (out1.getRequirements().length == 0) {
							continue;
						}
						ResourceBasket rb1 = out1.getRequirements()[0];
						int ind1 = r2pos.get(rb1.type);
						compl[ind0][ind1] += rb0.value * rb1.value;
					}
				}
			}
		}
		int notGoodCount = 0;
		for (int[] g : groups) {
			for (int p : g) {
				double ingr = 0.0;
				for (int q : g) {
					ingr += compl[p][q];
				}
				double outgr = 0.0;
				for (int q = 0; q < n; q++) {
					if (Arrays.binarySearch(g, q) > -1) continue;
					outgr += compl[p][q];
				}
				ingr /= g.length;
				outgr /= n - g.length;
				boolean good = ingr > outgr * mul;
				if (trueAssert) {
					Assert.assertTrue(good);
				}
				if (!good) {
					notGoodCount++;
				}
			}
		}
		return notGoodCount;
	}
}
